#include <stdio.h>
#include <CoOS.h>
#include "NUC123.h"
#include "shell.h"

#define W25X_INS_FAST_READ      0x0B
#define W25X_INS_GET_ID         0x90
#define W25X_INS_WRITE_ENABLE   0x06
#define W25X_INS_WRITE_DISABLE  0x04
#define W25X_INS_PAGE_PROGRAM   0x02
#define W25X_INS_READ_SR        0x05
#define W25X_INS_READ_DATA      0x03
#define W25X_INS_CHIP_ERASE     0xc7
#define W25X_INS_SECTOR_ERASE   0x20
#define W25X_INS_BLOCK_ERASE    0xd8
#define W25X_INS_POWER_DOWN     0xb9
#define W25X_INS_WAKE_UP        0xab
#define W25X_INS_WRITE_SR       0x01
#define W25X_INS_FAST_READ_DUAL_OUT    0x3b
#define W25X_INS_JEDEC_ID	0x9f

__STATIC_INLINE void WRITE_SPI(U32 u32Data)
{
	while(SPI_IS_BUSY(SPI0));
	SPI_WRITE_TX0(SPI0, u32Data);
	SPI_TRIGGER(SPI0);
	while(SPI_IS_BUSY(SPI0));
}
	
__STATIC_INLINE U32 READ_SPI(void)
{
	while(SPI_IS_BUSY(SPI0));
	SPI_WRITE_TX0(SPI0, 0xFF);
	SPI_TRIGGER(SPI0);
	while(SPI_IS_BUSY(SPI0));
	return SPI_READ_RX0(SPI0);
}

void SPIFlash_Init(void)
{
	 SPI_Open(SPI0, SPI_MASTER, SPI_MODE_0, 8, 2000);
	 //SPI_EnableAutoSS(SPI0, SPI_SS0, SPI_SS_ACTIVE_LOW);
	 SPI_DisableAutoSS(SPI0);
	 SPI_DisableFIFO(SPI0);
}

U16 SPIFlash_GetID(void)
{
	U16 ID = 0;
	SPI_SET_SS0_LOW(SPI0);
	WRITE_SPI(W25X_INS_GET_ID);
	WRITE_SPI(0x00);
	WRITE_SPI(0x00);
	WRITE_SPI(0x00);
	ID = READ_SPI() & 0xFF;
	ID <<= 8;
	ID |= READ_SPI()&0xFF;
	SPI_SET_SS0_HIGH(SPI0);
	return ID;
}

U8 SPIFlash_GetStauts(void)
{
	U8 u8Stauts = 0;
	SPI_SET_SS0_LOW(SPI0);
	WRITE_SPI(W25X_INS_READ_SR);
	u8Stauts = READ_SPI() & 0xFF;
	SPI_SET_SS0_HIGH(SPI0);
	return u8Stauts;
}

void SPIFlash_EnableWrite(BOOL b8Enable)
{
	SPI_SET_SS0_LOW(SPI0);
	if(b8Enable == TRUE)
	{
		WRITE_SPI(W25X_INS_WRITE_ENABLE);
	}
	else
	{
		WRITE_SPI(W25X_INS_WRITE_DISABLE);
	}
	SPI_SET_SS0_HIGH(SPI0);
}

U8 SPIFlash_SetStauts(U8 u8Stauts)
{
	SPIFlash_EnableWrite(TRUE);
	SPI_SET_SS0_LOW(SPI0);
	WRITE_SPI(W25X_INS_WRITE_SR);
	WRITE_SPI(u8Stauts);
	SPI_SET_SS0_HIGH(SPI0);
	return u8Stauts;
}

void SPIFlash_PowerOn(BOOL b8Enable)
{
	SPI_SET_SS0_LOW(SPI0);
	if(b8Enable == TRUE)
	{
		WRITE_SPI(W25X_INS_WAKE_UP);
	}
	else
	{
		WRITE_SPI(W25X_INS_POWER_DOWN);
	}
	SPI_SET_SS0_HIGH(SPI0);
}

void SPIFlash_ChipErase(void)
{
	SPIFlash_EnableWrite(TRUE);
	SPI_SET_SS0_LOW(SPI0);

	WRITE_SPI(W25X_INS_CHIP_ERASE);
	
	SPI_SET_SS0_HIGH(SPI0);
}

void SPIFlash_BlockErase(U32 u32Addr)
{
	U8 u8Addr[3];
	
	u8Addr[0] = (U8)(u32Addr>>16);
	u8Addr[1] = (U8)(u32Addr>>8);
	u8Addr[2] = (U8)(u32Addr);

	SPIFlash_EnableWrite(TRUE);
	SPI_SET_SS0_LOW(SPI0);

	WRITE_SPI(W25X_INS_BLOCK_ERASE);
	WRITE_SPI(u8Addr[0]);
	WRITE_SPI(u8Addr[1]);
	WRITE_SPI(u8Addr[2]);
	
	SPI_SET_SS0_HIGH(SPI0);
}


void SPIFlash_SectorErase(U32 u32Addr)
{
	U8 u8Addr[3];
	
	u8Addr[0] = (U8)(u32Addr>>16);
	u8Addr[1] = (U8)(u32Addr>>8);
	u8Addr[2] = (U8)(u32Addr);

	SPIFlash_EnableWrite(TRUE);
	SPI_SET_SS0_LOW(SPI0);

	WRITE_SPI(W25X_INS_SECTOR_ERASE);
	WRITE_SPI(u8Addr[0]);
	WRITE_SPI(u8Addr[1]);
	WRITE_SPI(u8Addr[2]);
	
	SPI_SET_SS0_HIGH(SPI0);
}

static void _spi_page_write(U8 *pu8Buf, U32 u32Addr, U16 u16Len)
{
	U16 u16i;
	SPIFlash_EnableWrite(TRUE);
	SPI_SET_SS0_LOW(SPI0);

	WRITE_SPI(W25X_INS_PAGE_PROGRAM);
	WRITE_SPI((u32Addr>>16)&0xFF);
	WRITE_SPI((u32Addr>>8)&0xFF);
	WRITE_SPI(u32Addr&0xFF);

	for(u16i = 0; u16i < u16Len; u16i++)
	{
		WRITE_SPI(&pu8Buf[u16i]); 
	}
	
	SPI_SET_SS0_HIGH(SPI0);
}

U16 SPIFlash_Write(U8 *pu8Buf, U32	u32Addr, U16 u16Len)
{
	U8 *pu8Wbuff;
	U16 u16First, u16Last, u16PageNum, u16i;
	U32 u32WAddr;

	u16First = u32Addr %256;
	u32WAddr = u32Addr - u16First;
	u16First = 256 -u16First;

	pu8Wbuff = pu8Buf;

	if(u16First < u16Len) 
  	{
    		u16PageNum  = (u16Len - u16First) / 256;
    		u16Last = (u16Len - u16First) % 256;
  	} 
  	else 
  	{
    		u16First = u16Len;
    		u16PageNum = 0;
    		u16Last = 0;
  	}

	if(u16First != 0)
	{
		_spi_page_write(pu8Buf, u32Addr, u16First);
		u32WAddr += 256;
		pu8Wbuff += u16First;
	}

	for(u16i = 0; u16i < u16PageNum; u16i++)
	{
		_spi_page_write(pu8Wbuff, u32WAddr, 256); 
		u32WAddr += 256;
		pu8Wbuff += 256;
	}

	if(u16Last != 0)
	{
		_spi_page_write(pu8Wbuff, u32WAddr, u16Last); 
	}

	return u16Len;
}

void SPIFlash_Read(U8 *pu8Buf, U32	u32Addr, U16 u16Len)
{
	U16 u16i;
	
	SPI_SET_SS0_LOW(SPI0);
	WRITE_SPI(W25X_INS_READ_DATA);
	WRITE_SPI((u32Addr>>16)&0xFF);
	WRITE_SPI((u32Addr>>8)&0xFF);
	WRITE_SPI(u32Addr&0xFF);

	for(u16i = 0; u16i < u16Len; u16i++)
	{
		pu8Buf[u16i] = READ_SPI(); 
	}
	SPI_SET_SS0_HIGH(SPI0);
}

void spi_test(char *p0, char *p1, char *p2)
{
	U8 u8cmd = atoi(p0);
	switch(u8cmd)
	{
		case 0:
			printf("ID %x\n", SPIFlash_GetID());
			break;
		case 1:
			printf("RS %x\n", SPIFlash_GetStauts());
			break;
		case 2:
			SPIFlash_EnableWrite(TRUE);
			SPIFlash_SetStauts(0);
			printf("WS %x\n", SPIFlash_GetStauts());
			break;
		case 3:
			SPIFlash_PowerOn(0);
			break;
		case 4:
			SPIFlash_PowerOn(1);
			break;
		case 5:
			{
				U16 u16i;
				U8 au8Data[256];
				memset(au8Data, 0x5a, 256);
				SPIFlash_SectorErase(0);
				SPIFlash_Write(au8Data, 0, 256);
				memset(au8Data, 0, 256);
				SPIFlash_Read(au8Data, 0, 256);
				for(u16i = 0; u16i<256; u16i++)
				{
					if(u16i % 16 == 0)
					{
						printf("\n");
					}
					printf("%02x ", au8Data[u16i]);
				}
				printf("\n");
			}
			break;
		case 6:
			SPIFlash_SectorErase(4096);
			break;
		case 7:
			{
				U16 u16i;
				
				U8 au8Data[256];
				memset(au8Data, 0, 256);
				SPIFlash_Read(au8Data, 4096, 256);
				for(u16i = 0; u16i<256; u16i++)
				{
					if(u16i % 16 == 0)
					{
						printf("\n");
					}
					printf("%02x ", au8Data[u16i]);
				}
				printf("\n");
			}
			break;
		case 8:
			{
				U16 u16i;
				U8 au8Data[256];
				U8 data = atoi(p1);
				memset(au8Data, data, 256);
				printf("Write %x\n", data);
				SPIFlash_Write(au8Data, 4096, 256);
			}
			break;
		default:
			break;
	}
}

//SHELL_CMD(spi, spi_test);


